package com.formula.distribution.lock.core;

import cn.hutool.core.util.StrUtil;
import com.formula.distribution.lock.annotation.Lock;
import com.formula.distribution.lock.annotation.LockParam;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * @author:bo
 * @email:
 * @date 2018/12/25
 * @introduce 根据用户自定义的业务 获取key
 **/
public class BusinessKeyProvider {

    private ParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    private ExpressionParser parser = new SpelExpressionParser();


    public String getKeyName(ProceedingJoinPoint point, Lock lock) {
        List<String> keyList = new ArrayList<>();

        Method method = getMethod(point);
        List<String> definitionKeys = getSpeDefinitionKey(lock.keys(), method, point.getArgs());
        keyList.addAll(definitionKeys);

        List<String> parameterKeys = getParameterKey(method.getParameters(), point.getArgs());
        keyList.addAll(parameterKeys);

        return StringUtils.collectionToDelimitedString(keyList, "", "-", "");
    }


    private Method getMethod(ProceedingJoinPoint point) {
        MethodSignature signature = (MethodSignature) point.getSignature();

        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = point.getTarget().getClass().getDeclaredMethod(signature.getName(), method.getParameterTypes());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return method;
    }


    private List<String> getSpeDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {

        List<String> definitionKeyList = new ArrayList<>();
        Arrays.
                stream(definitionKeys).
                filter(definitionKey -> StrUtil.isNotBlank(definitionKey)).
                forEach(
                        definitionKey -> {
                            EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, nameDiscoverer);
                            String key = parser.parseExpression(definitionKey).getValue(context).toString();
                            definitionKeyList.add(key);
                        }
                );

        return definitionKeyList;
    }


    private List<String> getParameterKey(Parameter[] parameters, Object[] parameterValues) {

        List<String> parameterKeyList = new ArrayList<>();

        for (int i = 0; i < parameters.length; i++) {
            if (parameters[i].getAnnotation(LockParam.class) != null) {
                LockParam keyAnnotation = parameters[i].getAnnotation(LockParam.class);
                if (keyAnnotation.value().isEmpty()) {
                    parameterKeyList.add(parameterValues[i].toString());
                } else {
                    StandardEvaluationContext context = new StandardEvaluationContext(parameterValues[i]);
                    String key = parser.parseExpression(keyAnnotation.value()).getValue(context).toString();
                    parameterKeyList.add(key);
                }
            }
        }
        return parameterKeyList;
    }


}
